Technical Note TN2027
How to write a JDBC Plugin (With Example)

目次

WebObjects 5 では、データベースへのアクセスはすべて、JDBC アダプタ を通じて行われます。JDBC は、データベースベンダにより幅広くサポートされていますが、すべてのデータソースが、まったく同じように動作するわけではありません。したがって JDBC アダプタは、特定のデータベース用またはドライバ用に対応するために、ヘルパークラス、つまり JDBCPlugIn のサブクラスを使います。WebObjects 5 のための JDBC アダプタ には Oracle 8i と OpenBase に対するサポートが組み込まれて出荷されます。ほかのデータソースには、カスタムのプラグインによるサポートの追加が必要になる場合があります。このテクニカルノートでは、カスタムのプラグインの作成方法について説明します。

[2001 年 6 月 11 日]






2 種類の JDBC アダプタ

WebObjects 5 では、すべてのフレームワークがピュア Java で開発されており、WebObjects アプリケーションは必然的に、ピュア Java を使います。しかし、歴史的な背景から、EOModeler など開発ツールの一部は Objective-C で開発されています。このため、EOModeler は、JDBC へのアクセスをサポートするために、Java ブリッジを使う必要があります。つまり、システムには事実上、通常の Java ランタイム用と EOModeler 専用の 2 種類の JDBC アダプタが存在することになります。この図式はほとんどのユーザにとっては見えませんが、プラグインの開発者にとってはそれだけに留まらない意味があります。

本文書では、ランタイムで使用されるピュア Java 対応の JDBC アダプタ を、JDBC アダプタ と呼びます(このフレームワークは、システムには /System/Library/Frameworks/JavaJDBCAdaptor.framework としてインストールされています)。もう 1 つのアダプタは、ブリッジ JDBC アダプタで、これを使うのは、 EOModeler だけです(この「ブリッジ」アダプタは、システム上に /System/Library/Frameworks/JDBCEOAdaptor.framework としてインストールされています)。ブリッジ JDBC アダプタは、ピュア Java の WebObjects フレームワークを使いません。 その代わりに、Foundation 用の Java ラッパ、および Objective-C の専用の EOAccess を使います。これらのラッパは、ブリッジ JDBC アダプタ が EOModeler に対応して動作するための十分な機能を提供しますが、Java ブリッジのための完全な EOF の実装ではありません。これらは、WebObjects 開発ツールに専用のものとみなされています。

念のために書いておくと、WebObjects アプリケーションには必ず、JDBCEOAdaptor ではなく JavaJDBCAdaptor をリンクします。以下で説明する EOModeler と EOModeler のためのカスタムのバンドルだけが、JDBCEOAdaptor を使います。

先頭に戻る

JDBCPlugIn

最初に、WebObjects アプリケーションが使う JDBCPlugIn のサブクラスの作成方法について説明し、その後、作成したプラグイン機能を EOModeler で利用できるようにする方法について説明します。

まず、com.webobjects.jdbcadaptor.JDBCPlugIn のサブクラスを作る必要があります。詳細については、本文書付録のメソッドの説明を参照してください。プラグインは、アプリケーションのコードに追加することも、アプリケーションとリンクするフレームワークの一部として作成することもできます。

作成したモデルの接続辞書で、「プラグイン」キーとして JDBCPlugIn のサブクラスの名前を指定する必要があります。その値がクラスの完全限定名でない場合、アダプタは com.webobjects.jdbcadaptor パッケージの中で、対応するクラスを探します。そこになかった場合は、指定された名前の後に「PlugIn」を付けて探します。

プラグインクラスは主に、接続辞書にある「プラグイン」のエントリによって決まります。値がない場合、プラグイン名は、接続辞書の URL のサブプロトコルから類推されます。JDBC の URL は必ず jdbc: で始まり、その後にサププロトコル名、もう 1 つのコロン、URL の残りの部分が続きます。普通にプラグインを類推する場合は、サププロトコル名の最初の文字を大文字にし、「PlugIn」を追加します。たとえば、「jdbc:mydb:foobar」は、プラグイン名が「com.webobjects.jdbcadaptor.MydbPlugIn」であるとみなされます。この名前の類推法は、WOApplication の中でスタティックメソッド、 JDBCPlugIn.setPlugInNameForSubprotocol() を使って制御できます。

プラグインが選択されると、アダプタは JDBC ドライバをロードしようとします。このドライバは通常、プラグインの defaultDriverName() の実装に従って選択されます。ユーザは、接続辞書で「ドライバ」に値を設定することにより、この選択をオーバーライドできます。

データベースの行のフェッチ、挿入、更新などの一般的な操作は通常、JDBC メソッドを使って行います。 カスタムのプラグインは、データソースの特殊な構文を処理するために、defaultExpressionClass() を使って独自の SQL 式クラスを提供できます。 一般的には、カスタムの JDBCExpression サブクラスと併せて、デフォルトの式ファクトリ(JDBCExpressionFactory)を使うとよいでしょう。

テーブル作成またはリバースエンジニアリングのいくつかの面では、カスタムのコードが必要です。プラグインにはリバースエンジニアリングを制御するいくつかのメソッドがあります。さらにカスタムのプラグインは、専用のスキーマ同期関数を提供するために、createSychronizationFactory() を使って、 カスタムのサブクラス、EOSynchronizationFactory を提供することもできます。WOApplication が実行時にスキーマの同期を必要とすることはまれなので、スキーマ同期に関して基本的には何もしないデフォルトの実装を使うのは、まったく妥当です。

先頭に戻る

EOModeler でのカスタムのプラグインの使用

前述したように、EOModeler は、ブリッジ JDBC アダプタ を使い、ピュア Java のプラグインへはアクセスしません。プラグインコードを EOModeler 内で機能させるには、特殊な NSBundle を提供する必要があります。 Project Builder 形式のサンプルプロジェクトは、ピュア Java のソースのコピーをブリッジプラグインバンドルに変更する方法を示します(WOApplication プロジェクトにはまだピュア Java のバージョンを使います)。

最初の一歩は、Project Builder で新しいプロジェクトを作成することです。このプロジェクトは、Cocoa バンドルでなければなりません。詳細についてはサンプルプロジェクトを参考にしてください。

次にいくつかの注意すべき点を示します。

たとえソースコードが含まれていなくても、main.c ファイルを用意する必要があります。 これによりバンドルが正しく作成され、EOModeler にブリッジ対応 Java プラグイン をロードすることができます。

このバンドルは、JDBCEOAdaptor.framework および Foundation.framework とリンクできなければなりません。

EOModeler で利用できるようにするブリッジ JDBC プラグインごとに Java ファイルが 1 つ必要です。1 つのバンドルに複数のプラグインを含めることができます。

プラグインコードを最上位のパッケージに入れます。つまり、プラグインソースファイルでは package 文は使いません。

インポートの対象として、ピュア WebObjects 5 パッケージではなく、Java ラッパのパッケージを使います。次に典型的なリストを示します。

  import com.apple.cocoa.foundation.*;  // com.webobjects.foundation ではない
  import com.apple.yellow.eoaccess.*;  // com.webobjects.eoaccess ではない
  import com.apple.yellow.eocontrol.*;  // com.webobjects.eocontrol ではない

NSArray または NSDictionary などのコレクションを返すラップされたメソッドのいくつかは、コレクションが空であることを示すためにヌルを返すこともあります(Objective-C の慣用)。したがって、ピュア Java ベースの WebObjects 5 のコードよりもさらにヌルに対応できるコードにする必要があります。返された NSArray または NSDictionary を使う前に必ずヌルであるかどうかをチェックします。コードでは、ヌルではなく、空のコレクションを返すようにしなければなりません。

ピュア Java ソースを、ブリッジ Java に変換するには、Java ラッパに対応させるためにほかのいくつかの変更が必要です。

  "NSKeyValueCoding.NullValue" を "HackedUtils.NullValue" で置き換えます
  "NSArray.EmptyArray" を "HackedUtils.EmptyArray" で置き換えます
  "NSDictionary.EmptyDictionary" を "HackedUtils.EmptyDictionary" で置き換えます

作成するバンドルは、.EOMplugin という拡張子の付いたファイル名でなければなりません。また、EOModeler がバンドルを探す場所(通常は /Developer/EOMBundles/)にインストールする必要があります。ディスク上のほかの場所に格納する場合は、シンボリックリンクが使えます。

ブリッジ対応 JDBCPlugIns は、EOModeler だけが使います。 WebObjects 5 のランタイムは、アプリケーションにピュア Java の JDBCPlugIn がリンクされていることを要求します。ソースコードは似ていますが、バイナリコードは同じではありません。

Windows で使用するブリッジ JDBCPlugIn の作成はもう少し複雑です(ピュア Java JDBCPlugIn は当然ながらどのプラットフォームでもまったく同じです)。Windows 用の Java ブリッジは、JDK 1.1 しかサポートしていないので、JDK 1.2(または Java 2)のサポートを必要とする最新の JDBC ドライバを使用できません。新しい Project Builder は使えないので、ProjectBuilderWO を使わなければなりません。また Windows の Java ラッパの場合は、パッケージ構成も、少し異なります。Foundation ラッパは、 package com.apple.yellow.foundation にあります(cocoa ではなく yellow となっています)。

カスタムのプラグインは通常、独自の式クラス(JDBCExpression のサブクラスでなければならない)を使います。ブリッジプラグインへの EOModeler のアクセスをサポートするには、通常の API に加え、ブリッジ式クラスに次のメソッドを実装する必要があります。式クラスはおそらくプラグインクラスのスタティックなインナークラスになるでしょう。詳細については、次のコード例を参照してください。

  // Java ブリッジサポートのための CustomPlugIn のインナークラスのコードの一部
  public static クラス、CustomExpression は、JDBCExpression を拡張
    // 引数のないコンストラクタ
    public CustomExpression()
    public static EOSQLExpression sharedInstance()
    public static EOSynchronizationFactory sharedSyncFactory()
    public Class _synchronizationFactoryClass()

独自の EOSynchronizationFactory サブクラスを実装するときは通常、プラグインのもう 1 つのスタティックなインナークラスとします。引数のないコンストラクタをクラスに追加しなければなりません(ブリッジ版の場合のみ)。詳細については、次のコード例を参照してください。

 // Java ブリッジサポートのための CustomPlugIn のインナークラスのコードの一部
  public static class CustomSynchronizationFactory extends EOSynchronizationFactory
    // 引数のないコンストラクタ
    public CustomSynchronizationFactory()

先頭に戻る

デバッグ

ブリッジプラグインの開発時、 EOAdaptorDebuggingEnabled を有効にすると役に立つかもしれません。このための簡単な方法は、Terminal ウィンドウで次のコマンドを実行することです(% は csh プロンプトとします)。

% defaults write NSGlobalDomain EOAdaptorDebugEnabled YES

これにより、ブリッジ JDBC アダプタ は、追加情報を Console ウィンドウに出力します。このログ出力は、Terminal ウィンドウで別のコマンドを入力することによって中止できます。

% defaults write NSGlobalDomain EOAdaptorDebugEnabled NO

詳細な情報については、defaults の man ページを参照してください( ちなみに、 EOModeler のドメインは com.apple.EOModeler です)。

先頭に戻る

要約

このテクニカルノートでは、WebObjects 5 JDBC アダプタのために JDBCPlugIn を作成する方法について説明しました。また、EOModeler 内で使用できるように、ブリッジプラグインを NSBundle にする方法についても説明しました。サンプルコードでは、EOModeler 向けの特別な NSBundle を作成する方法を示します。

先頭に戻る

ダウンロード

Acrobat gif

このテクニカルノートの PDF 版

ダウンロード

Redbook gif

Project Builder のプロジェクト(24K)

ダウンロード tn2027.tgz


先頭に戻る

付録 A:JDBCPlugIn のメソッドの説明

// このファイルにはコメントとメソッドのシグネチャのみが含まれている。
// 今後の WebObjects リリースには、JDBCAdaptor のために通常の JavaDoc
// が含まれる

// package com.webobjects.jdbcadaptor;
// public class JDBCPlugIn


/**
* コンストラクタ、通常はサブクラスからスーパークラスを呼び出すだけである
*/
public JDBCPlugIn(JDBCAdaptor adaptor)


/**
* データベースを識別する文字列を返す
*/
public String databaseProductName()


/**
 * このプラグインが使用するドライバクラスの完全限定名を返す。 
 * アダプタは、接続を確立するときに、このクラスをロードしようとする
 */
public String defaultDriverName()


/**
 * デフォルトでは、接続辞書から URL を返すだけである。
 * サブクラスは通常、これをオーバーライドしない
 */
public String connectionURL()


/**
 * サブクラスは通常、オーバーライドして独自の式クラスを使う
 */
public Class defaultExpressionClass()


/**
 * デフォルトでは JDBCExpresionFactory を返す。 
 * サブクラスがこれをオーバーライドする必要はほとんどない
 */
public EOSQLExpressionFactory createExpressionFactory()


/**
 * サブクラスは通常、オーバーライドして独自の同期化ファクトリを使用する
 */
public EOSynchronizationFactory createSynchronizationFactory()


/**
 * デフォルトでは "EO_PK_TABLE" を返す。サブクラスは通常、
 * これをオーバーライドしない。newPrimaryKeys() も参照すること
 */
public String primaryKeyTableName()


// 次の 4 つのメソッドは JDBCChannel.describeTableNames() により
// 内部的に呼び出される

/**
 * データベースで利用可能なテーブルのリストを取得するために使用すべき
 *  SQL ステートメントを返す。デフォルトではヌルを返す。その場合
 * JDBC の 標準の getTables() メソッドが使用される
 */
public String sqlStatementForGettingTableNames()


/**
 * JDBC の getTables() メソッドの呼び出しの中で、スキーマのパターンを
 * 記述するために使用すべき文字列を返す。デフォルトではヌルを返す
 */
public String wildcardPatternForSchema()


/**
 * JDBC の getTables() メソッドの呼び出しの中で、テープルのパターンを
 * 記述するために使用すべき文字列を返す。デフォルトでは % を返す
 */
public String wildcardPatternForTables()


/**
 * JDBC の getTables() メソッドへの呼び出しに使用すべきタイプを定義する
言語配列または文字列を返す。デフォルトでは { "TABLE",
 * "VIEW", "ALIAS", "SYNONYM"}; を返す
 */
public String[] tableTypes()




/**
 * JDBC の getColumns() メソッドの中で、
 * カラムパターン名を記述するために使用すべき文字列を返す。 
 * デフォルトの実装では % を返す
 */
public String wildcardPatternForAttributes()


/**
 * JDBC の標準メソッド、getProcedures() を使って参照される
 * ストアドプロシージャのカタログのためのパターン文字列を返す。
 * デフォルトはヌル
 */
public String storedProcedureCatalogPattern()


/**
 * JDBC 標準メソッド、getProcedures() を使って参照される
 * ストアドプロシージャのスキーマのためのパターン文字列を返す。
 * デフォルトはヌル
 */
public String storedProcedureSchemaPattern()


/**
 * このメソッドは、新しく挿入された 'entity' の種類の 'count' 個のオブジェクト
 * に対して、NSDictionary の NSArray を返す。
 * 各 NSDictionary は、エンティティの主キーとして
 * 使用するのに適しています。デフォルトの実装では、primaryKeyTableName() が返す、ルートエンティティの名前と
 * テーブルに挿入された最新の主キーを含む 
 * テーブル名が使われる。エンティティ名に対応する行が
 * 存在しない場合には、自動的に作成される。テーブルが存在しない場合にも、
 * 自動的に作成される。主キーに複数の属性が含まれている場合または
 * 数値でない場合、あるいはアダプタが 'count' 個の新しい主キー値を
 * 提供できなかった場合は、
 * デフォルトの実装ではヌルを返す
 */
public NSArray newPrimaryKeys(int count, EOEntity entity, JDBCChannel channel)


/**
 * データソースに関するメタ情報(タイプ情報を含む)
 * の NSDictionary を返す。サブクラスは通常このメソッドを
 * オーバーライドする必要はないが、必要な場合は
 * 一般的にはスーパークラスを呼び出さなければならない
 */
public NSDictionary jdbcInfo()


/**
 * デフォルトでは false を返す。サブクラスがこれをオーバーライドする場合もある。
 * 疑似カラムは、CREATE TABLE ステートメントで定義されたカラム以外に、
 * データベースにより作成される追加のカラムである。
 * 擬似カラムは、内部の管理のために使われ、通常は読み取り専用である。
 * このメソッドがカラム名に対して true を返す場合は、アダプタはそのカラムの
 * リバースエンジニアリングは行わない
 */
public boolean isPseudoColumnName(String columnName)




// 次のメソッドはオーバーライドしないこと...

/**
* 必要に応じて createExpressionFactory() を呼び出し、結果をキャッシュする。
* サブクラスはこれをオーバーライドしてはならない
*/
public EOSQLExpressionFactory expressionFactory()

/**
 * 必要に応じて createSynchronizationFactory() を呼び出し、結果をキャッシュする。
 * サブクラスはこれをオーバーライドしてはならない
 */
public EOSynchronizationFactory synchronizationFactory()

/**
 * このプラグインを使用している JDBCAdaptor を返す
 */
public JDBCAdaptor adaptor()



// プラグイン名の自動類推のためのユーティリティメソッド

/**
 * 使用するプラグインを類推するために、
 * サブプロトコルと PluginName の内部的な対応付けを設定する
 */
public static void setPlugInNameForSubprotocol(String pluginName, String subprotocol)


/**
 * サブプロトコルに使用する特別なプラグインの内部的な対応付け
 * をクリアし、デフォルトの動作に戻す
 */
public static void removePlugInNameForSubprotocol(String subprotocol)

先頭に戻る